/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.net;

import java.net.*;
import java.util.Formatter;
import java.util.Locale;
import sun.net.util.IPAddressUtil;

/**
 * Parses a string containing a host/domain name and port range
 */
class HostPortrange {

  String hostname;
  String scheme;
  int[] portrange;

  boolean wildcard;
  boolean literal;
  boolean ipv6, ipv4;
  static final int PORT_MIN = 0;
  static final int PORT_MAX = (1 << 16) - 1;

  boolean equals(HostPortrange that) {
    return this.hostname.equals(that.hostname)
        && this.portrange[0] == that.portrange[0]
        && this.portrange[1] == that.portrange[1]
        && this.wildcard == that.wildcard
        && this.literal == that.literal;
  }

  public int hashCode() {
    return hostname.hashCode() + portrange[0] + portrange[1];
  }

  HostPortrange(String scheme, String str) {
    // Parse the host name.  A name has up to three components, the
    // hostname, a port number, or two numbers representing a port
    // range.   "www.sun.com:8080-9090" is a valid host name.

    // With IPv6 an address can be 2010:836B:4179::836B:4179
    // An IPv6 address needs to be enclose in []
    // For ex: [2010:836B:4179::836B:4179]:8080-9090
    // Refer to RFC 2732 for more information.

    // first separate string into two fields: hoststr, portstr
    String hoststr, portstr = null;
    this.scheme = scheme;

    // check for IPv6 address
    if (str.charAt(0) == '[') {
      ipv6 = literal = true;
      int rb = str.indexOf(']');
      if (rb != -1) {
        hoststr = str.substring(1, rb);
      } else {
        throw new IllegalArgumentException("invalid IPv6 address: " + str);
      }
      int sep = str.indexOf(':', rb + 1);
      if (sep != -1 && str.length() > sep) {
        portstr = str.substring(sep + 1);
      }
      // need to normalize hoststr now
      byte[] ip = IPAddressUtil.textToNumericFormatV6(hoststr);
      if (ip == null) {
        throw new IllegalArgumentException("illegal IPv6 address");
      }
      StringBuilder sb = new StringBuilder();
      Formatter formatter = new Formatter(sb, Locale.US);
      formatter.format("%02x%02x:%02x%02x:%02x%02x:%02x"
              + "%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
          ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8],
          ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]);
      hostname = sb.toString();
    } else {
      // not IPv6 therefore ':' is the port separator

      int sep = str.indexOf(':');
      if (sep != -1 && str.length() > sep) {
        hoststr = str.substring(0, sep);
        portstr = str.substring(sep + 1);
      } else {
        hoststr = sep == -1 ? str : str.substring(0, sep);
      }
      // is this a domain wildcard specification?
      if (hoststr.lastIndexOf('*') > 0) {
        throw new IllegalArgumentException("invalid host wildcard specification");
      } else if (hoststr.startsWith("*")) {
        wildcard = true;
        if (hoststr.equals("*")) {
          hoststr = "";
        } else if (hoststr.startsWith("*.")) {
          hoststr = toLowerCase(hoststr.substring(1));
        } else {
          throw new IllegalArgumentException("invalid host wildcard specification");
        }
      } else {
        // check if ipv4 (if rightmost label a number)
        // The normal way to specify ipv4 is 4 decimal labels
        // but actually three, two or single label formats valid also
        // So, we recognise ipv4 by just testing the rightmost label
        // being a number.
        int lastdot = hoststr.lastIndexOf('.');
        if (lastdot != -1 && (hoststr.length() > 1)) {
          boolean ipv4 = true;

          for (int i = lastdot + 1, len = hoststr.length(); i < len; i++) {
            char c = hoststr.charAt(i);
            if (c < '0' || c > '9') {
              ipv4 = false;
              break;
            }
          }
          this.ipv4 = this.literal = ipv4;
          if (ipv4) {
            byte[] ip = IPAddressUtil.textToNumericFormatV4(hoststr);
            if (ip == null) {
              throw new IllegalArgumentException("illegal IPv4 address");
            }
            StringBuilder sb = new StringBuilder();
            Formatter formatter = new Formatter(sb, Locale.US);
            formatter.format("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
            hoststr = sb.toString();
          } else {
            // regular domain name
            hoststr = toLowerCase(hoststr);
          }
        }
      }
      hostname = hoststr;
    }

    try {
      portrange = parsePort(portstr);
    } catch (Exception e) {
      throw new IllegalArgumentException("invalid port range: " + portstr);
    }
  }

  static final int CASE_DIFF = 'A' - 'a';

  /**
   * Convert to lower case, and check that all chars are ascii
   * alphanumeric, '-' or '.' only.
   */
  static String toLowerCase(String s) {
    int len = s.length();
    StringBuilder sb = null;

    for (int i = 0; i < len; i++) {
      char c = s.charAt(i);
      if ((c >= 'a' && c <= 'z') || (c == '.')) {
        if (sb != null) {
          sb.append(c);
        }
      } else if ((c >= '0' && c <= '9') || (c == '-')) {
        if (sb != null) {
          sb.append(c);
        }
      } else if (c >= 'A' && c <= 'Z') {
        if (sb == null) {
          sb = new StringBuilder(len);
          sb.append(s, 0, i);
        }
        sb.append((char) (c - CASE_DIFF));
      } else {
        throw new IllegalArgumentException("Invalid characters in hostname");
      }
    }
    return sb == null ? s : sb.toString();
  }


  public boolean literal() {
    return literal;
  }

  public boolean ipv4Literal() {
    return ipv4;
  }

  public boolean ipv6Literal() {
    return ipv6;
  }

  public String hostname() {
    return hostname;
  }

  public int[] portrange() {
    return portrange;
  }

  /**
   * returns true if the hostname part started with *
   * hostname returns the remaining part of the host component
   * eg "*.foo.com" -> ".foo.com" or "*" -> ""
   */
  public boolean wildcard() {
    return wildcard;
  }

  // these shouldn't leak outside the implementation
  final static int[] HTTP_PORT = {80, 80};
  final static int[] HTTPS_PORT = {443, 443};
  final static int[] NO_PORT = {-1, -1};

  int[] defaultPort() {
    if (scheme.equals("http")) {
      return HTTP_PORT;
    } else if (scheme.equals("https")) {
      return HTTPS_PORT;
    }
    return NO_PORT;
  }

  int[] parsePort(String port) {

    if (port == null || port.equals("")) {
      return defaultPort();
    }

    if (port.equals("*")) {
      return new int[]{PORT_MIN, PORT_MAX};
    }

    try {
      int dash = port.indexOf('-');

      if (dash == -1) {
        int p = Integer.parseInt(port);
        return new int[]{p, p};
      } else {
        String low = port.substring(0, dash);
        String high = port.substring(dash + 1);
        int l, h;

        if (low.equals("")) {
          l = PORT_MIN;
        } else {
          l = Integer.parseInt(low);
        }

        if (high.equals("")) {
          h = PORT_MAX;
        } else {
          h = Integer.parseInt(high);
        }
        if (l < 0 || h < 0 || h < l) {
          return defaultPort();
        }
        return new int[]{l, h};
      }
    } catch (IllegalArgumentException e) {
      return defaultPort();
    }
  }
}
